/*
Copyright (c) 2008,2009, David Beck

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "rt0d4_rx.h"
#include "rt0d4_rx_internal.h"
#include "secobj_priv.hh"
#include "conn_priv.hh"
#include "crypt_buf.hh"

#ifdef PRINT_ERRORS_TO_STDOUT
  #define E() \
    printf("ERROR[%s:%d]\n",__FILE__,__LINE__)
#else
  #define E()
#endif

/**
   @file rt0d4_rx.cc
   @brief The implementation of the rt0d4/Rx security model
*/

using rt0d4::sec::crypt_buf;
using rt0d4::rx::conn_priv;
using rt0d4::rx::secobj_priv;

/*********************************/
/* internal functions            */
/*********************************/

/**
   @brief Destroy security object (called by Rx)

   The name of the Rx callback is kind of misleading. This should be
   something like "Destroy Security Object"

   This is called at connection termination by Rx.
   If no connection references the security object than it can be released.
*/
RT0D4_CDECL
int rt0d4_rx_Close( struct rx_securityClass *aobj )
{
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;

  if( !aobj ) { E(); return ERR_INVALID_PARAM; }

  aobj->refCount--;

  if( aobj->refCount < 0 || !sepr )
  {
    E(); return ERR_INVALID_STATE;
  }
  else if( aobj->refCount == 0 )
  {
    /* free private data */
    /* won't free aobj: it is static */
    delete sepr;
    aobj->privateData = 0;
  }

  return 0;
}

/**
   @brief New connection (called by Rx)

   @li client: this is created when rx_NewConnection is called
   @li server: this is only called when the client actually makes a call
*/
RT0D4_CDECL
int rt0d4_rx_NewConnection(
                struct rx_securityClass *aobj,
                struct rx_connection *aconn )
{
  if( !aobj || !aconn ) { E(); return ERR_INVALID_PARAM; }

  if( aconn->securityData || aobj->privateData == NULL )
  {
    E(); return ERR_INVALID_STATE;
  }

  /* allocate aconn->securityData */
  conn_priv * cpriv = new conn_priv(
      *((secobj_priv *)aobj->privateData) );
  
  aconn->securityData = cpriv;
  
  /* set security header and tail values */
  crypt_buf cb;
  cpriv->set_authenticated(false);

  rx_SetSecurityHeaderSize(aconn, (cb.get_header_len() + cb.get_mac_len()) );
  rx_SetSecurityMaxTrailerSize(aconn, 0);

  /* adjust refcount */
  aobj->refCount++;
  return 0;
}

/**
   @brief Destroy connection (called by Rx)

   This function is called when a connection terminates. This
   enables to cleanup the internal data.
*/
RT0D4_CDECL
int rt0d4_rx_DestroyConnection(
                struct rx_securityClass *aobj,
                struct rx_connection *aconn )
{
  if( !aobj || !aconn )
  {
    E(); return ERR_INVALID_PARAM;
  }

  conn_priv * cp     = (conn_priv *)aconn->securityData;
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;
  
  if( cp == NULL || sepr == NULL )
  {
    E(); return ERR_INVALID_STATE;
  }

  void * condta = cp->get_conn_data();
  destroy_conn_data dcd = sepr->get_destroy_conn_data_cb();
  
  if( condta != NULL && dcd != NULL )
  {
    cp->set_conn_data(0);
    dcd(condta);
  }
  
  /* free aconn->securityData */  
  delete cp;
  aconn->securityData = 0;

  /* adjust refcount */
  aobj->refCount--;
  return 0;
}

/**
   @brief Create challenge (called by Rx)

   Called on the server side to create a challenge.
*/
RT0D4_CDECL
int rt0d4_rx_CreateChallenge(
                struct rx_securityClass * aobj,
                struct rx_connection * aconn)
{
  if( !aobj || !aconn ) { E(); return ERR_INVALID_PARAM; }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;

  if( !cpriv ) { E(); return ERR_INVALID_STATE; }

  cpriv->set_authenticated(false);

  if( !cpriv->create_challenge() )
  {
    /* adjust stat and return err */
    cpriv->n_create_challenge_err(true);
    E(); return ERR_CREATE_CHALLENGE;
  }

  /* adjust stat and retur success */
  cpriv->n_create_challenge_ok(true);
  return 0;
}

/**
   @brief Get challenge (called by Rx)

   This is the function that actually sends out the challenge data.
   This data must fit into one iovec size, that is based on the
   MTU. A challenge cannot be split.
*/
RT0D4_CDECL
int rt0d4_rx_GetChallenge(
                struct rx_securityClass * aobj,
                struct rx_connection * aconn,
                struct rx_packet * apacket)
{
  if( !aobj || !aconn || !apacket )
  {
    E(); return ERR_INVALID_PARAM;
  }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;

  if( !cpriv ) { E(); return ERR_INVALID_STATE; }

  size_t challenge_len = 0;
  const unsigned char * challenge = cpriv->get_challenge(challenge_len);

  if( !challenge_len || !challenge )
  {
    cpriv->n_get_challenge_err(true);
    E(); return ERR_CREATE_CHALLENGE;
  }

  rx_packetwrite( apacket,
    0,
    challenge_len,
    (char *)challenge );

  rx_SetDataSize(apacket, challenge_len);

  /* check if the created challenge can fit into one contigous packet */
  if( challenge_len <= (size_t)rx_Contiguous(apacket) )
  {
    cpriv->n_get_challenge_ok(true);
    return 0;
  }
  else
  {
    cpriv->n_get_challenge_err(true);
    E(); return ERR_CHALLENGE_TOO_BIG;
  }
}

/**
   @brief Check response (called by Rx)

   This is called on the server side when a response arrives
   from the client.

   This function expects to receive a contigous response packet,
   thus that should fit into one iovec.
*/
RT0D4_CDECL
int rt0d4_rx_CheckResponse(
                struct rx_securityClass * aobj,
                struct rx_connection * aconn,
                struct rx_packet * apacket)
{
  if( !aobj || !aconn || !apacket )
  {
    E(); return ERR_INVALID_PARAM;
  }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;

  if( !cpriv || !sepr ) { E(); return ERR_INVALID_STATE; }

  /* authenticated state defaults to false */
  cpriv->set_authenticated( false );

  /* push response in */
  if( cpriv->set_response(
                (unsigned char *)rx_DataOf(apacket),
                (size_t)rx_Contiguous(apacket)) == false )
  {
    cpriv->n_check_response_err( true );
    E(); return ERR_RESPONSE_INVALID;
  }

  if( cpriv->get_session_key() )
  {
    validate_peer_cert certcb = NULL;
    validate_peer_credentials credscb = NULL;

    if( (certcb = sepr->get_peer_cert_validation_cb()) != NULL )
    {
      size_t sz;
      const unsigned char * crt = cpriv->get_peer_cert(sz);
      if( !sz || !crt || (certcb(crt, sz) != 0) )
      {
        cpriv->n_check_response_err( true );
        E(); return ERR_CLIENT_CERT_BAD;
      }
    }

    if( (credscb = sepr->get_peer_credentials_validation_cb()) != NULL )
    {
      if( credscb(cpriv->get_peer_login(), cpriv->get_peer_password()) != 0 )
      {
        cpriv->n_check_response_err( true );
        E(); return ERR_CREDENTIALS_ERROR;
      }
    }
    
    create_conn_data ccd = sepr->get_create_conn_data_cb();
  
    if( ccd )
    {
      size_t cert_len = 0;
      const unsigned char * cert = cpriv->get_peer_cert(cert_len);
    
      void * cdata = ccd( cpriv->get_peer_login(),
                          cpriv->get_peer_password(),
                          cert,
                          cert_len );

      cpriv->set_conn_data(cdata);
    }
    
    cpriv->set_authenticated( true );
    cpriv->n_check_response_ok( true );
    return 0; /* SUCCESS */
  }
  else
  {
    cpriv->n_check_response_err( true );
    E(); return ERR_CREATE_SESSIONKEY;
  }
}

/**
   @brief Check authentication (called by Rx)

   This called zillion times on the server when communication
   takes place with the client.
*/
RT0D4_CDECL
int rt0d4_rx_CheckAuthentication(
                struct rx_securityClass * aobj,
                struct rx_connection * aconn)
{
  if( !aobj || !aconn ) { E(); return ERR_INVALID_PARAM; }

  int ret = 0;
  conn_priv * cpriv = (conn_priv *)aconn->securityData;

  if( !cpriv ) { E(); return ERR_INVALID_STATE; }

  if( cpriv->get_authenticated() )
  {
    cpriv->n_check_auth_ok(true);
    ret = 0;
  }
  else
  {
    cpriv->n_check_auth_err(true);
    ret = 1;
  }
  return ret;
}

/**
   @brief Encrypt packet helper (internal function)

   If no session key can be found, than it is an error.
 */
static int encrypt_packet_helper_ (
                struct rx_securityClass * aobj,
                struct rx_call * acall,
                struct rx_packet * apacket )
{
  struct rx_connection * aconn = (struct rx_connection *)rx_ConnectionOf(acall);
  conn_priv * cpriv = (conn_priv *)aconn->securityData;

  if( cpriv->get_session_key() == NULL ) { E(); return ERR_INVALID_STATE; }

  crypt_buf cb;

  unsigned char * data = (unsigned char *)(apacket->wirevec[1].iov_base);
  /* this includes the security header */
  int             sz   = rx_GetDataSize(apacket) +
                         rx_GetSecurityHeaderSize(aconn);
  int             szi  = apacket->wirevec[1].iov_len;
  int             szt  = 0;
  size_t          hl   = cb.get_header_len();
  size_t          ml   = cb.get_mac_len();

  /* init header */
  cb.init_crypt( data, cpriv->get_session_key(), true );
  data += (hl+ml);
  sz -= (hl+ml);
  szi -= (hl+ml);

  szt = (sz < szi ? sz : szi);

  /* first packet */
  cb.add_data( data,szt,true );
  sz -= szt;

  /* next packets */
  for( size_t i=2;i<(apacket->niovecs && sz>0);++i )
  {
    szi  = apacket->wirevec[i].iov_len;
    data = (unsigned char *)apacket->wirevec[i].iov_base;
    cb.add_data( data, szi, true );
    sz -= szi;
  }

  /* add MAC to the second part of the header */
  data = (unsigned char *)(apacket->wirevec[1].iov_base);
  data += hl;
  cb.finalize(data);

  // rx_SetDataSize( apacket, sz );
  return 0;
}

/**
   @brief Garble packet helper (internal function)

   This helper is called to fill the packet with 0xbd as an indication
   of bad packet.

   The reason why it is needed, that I discovered a potential security issue:

   @li the first client packet flags the connection initiation but at that time
        session key may not be generated yet so the prepare function has no key
        to use for encryption
   @li I couldn't find any way so far to delay the packet sending
        and still make the server generate and send the challenge
        so I chose to garble the packet instead
 */
static int garble_packet_helper_ (
                struct rx_securityClass * aobj,
                struct rx_call * acall,
                struct rx_packet * apacket )
{
  struct rx_connection * aconn = (struct rx_connection *)rx_ConnectionOf(acall);

  char * data = rx_DataOf(apacket);
  int    sz   = rx_Contiguous(apacket)+rx_GetSecurityHeaderSize(aconn);

  if( !data || !sz ) { E(); return ERR_INVALID_STATE; }

  memset(data,0xbd,sz);

  /* small amount of garbage is better */
  rx_SetDataSize(
              apacket,
              1 );

  /* next packets won't be garbled as they are not expected to be sent
     because of the data size set by rx_SetDataSize */
  return 0;
}

/* encrypt */

/**
   @brief Prepare packet (called by Rx)

   This function is called by Rx both at the client and the server side
   to encrypt outgoing packets.
*/
RT0D4_CDECL
int rt0d4_rx_PreparePacket (
                struct rx_securityClass * aobj,
                struct rx_call * acall,
                struct rx_packet * apacket )
{
  if( !aobj || !acall || !apacket ) { E(); return ERR_INVALID_PARAM; }

  struct rx_connection * aconn = (struct rx_connection *)rx_ConnectionOf(acall);

  if( !aconn ) { E(); return ERR_INVALID_STATE; }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;

  if( !cpriv || !sepr ) { E(); return ERR_INVALID_STATE; }

  if( encrypt_packet_helper_( aobj, acall, apacket ) != 0 )
  {
    /* NOTE : the packet is going to be garbled because of encryption error */
    garble_packet_helper_( aobj, acall, apacket );
    cpriv->n_prepare_packet_err( true );
    E(); return ERR_ENCRYPT_ERROR;
  }
  else
  {
    cpriv->n_prepare_packet_ok(true);
    return 0;
  }
}

/* decrypt */

/**
   @brief Check packet (called by Rx)

   This function is called by Rx on both the client and the server side in
   order to decrypt the incoming packet.
*/
RT0D4_CDECL
int rt0d4_rx_CheckPacket (
                struct rx_securityClass * aobj,
                struct rx_call * acall,
                struct rx_packet * apacket )
{
  if( !aobj || !acall || !apacket ) { E(); return ERR_INVALID_PARAM; }

  struct rx_connection * aconn = (struct rx_connection *)rx_ConnectionOf(acall);

  if( !aconn ) { E(); return ERR_INVALID_STATE; }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;

  if( !cpriv || !sepr ) { E(); return ERR_INVALID_STATE; }

  /* this get called even for the challenge/response!
     important: no decryption needed for that! */

  if( cpriv->n_send_packet_ok() == 0 && sepr->is_server() == false &&
      cpriv->n_get_response_ok() == 0 )
  {
    /* no packet sent out so far, so no response to decrypt */
    cpriv->n_check_packet_ok(true);
    return 0;
  }

  if( cpriv->get_session_key() == NULL )
  {
    cpriv->n_check_packet_err(true);
    /* if there is no session key, than the packet is not important
       I will garble that, to enforce error */
    garble_packet_helper_( aobj, acall, apacket );

    return 0; /* should be ERR_NO_SESSIONKEY ... but that would prevent
                 all further communication */
  }

  if( cpriv->get_authenticated() == false )
  {
    cpriv->n_check_packet_err(true);
    /* if the connection is not authenticated then no clear data should be
       sent out. I will garble that, to protect data */
    garble_packet_helper_( aobj, acall, apacket );

    E(); return ERR_NO_SECURITY; /* this would prevent all further communication */
  }

  crypt_buf cb;

  unsigned char * data = (unsigned char *)(apacket->wirevec[1].iov_base);
  /* this includes the security header */
  int             sz   = (rx_GetDataSize(apacket));
  int             szi  = apacket->wirevec[1].iov_len;
  int             szt  = 0;
  int             szv  = szi;
  size_t          hl   = cb.get_header_len();
  size_t          ml   = cb.get_mac_len();
  unsigned char   mac[64];
  unsigned int *  pi   = (unsigned int *)data;

  if( sizeof(mac) < ml )
  {
    cpriv->n_check_packet_err(true);
    E(); return ERR_BAD_SIZE_OF_MAC;
  }

  if( pi[0] == 0xbdbdbdbd )
  {
    if( pi[1] == 0xbdbdbdbd )
    {
      /* fake packet, sent without encryption
       * this must be allowed, because of the design flaw of
       * prepare */
      cpriv->n_check_packet_ok(true);
      return 0;
    }
    else
    {
      cpriv->n_check_packet_err(true);
      E(); return ERR_FAKE_PACKET;
    }
  }

  /* init header */
  cb.init_crypt( data, cpriv->get_session_key(), false );
  data += (hl+ml);
  sz -= (hl+ml);
  szi -= (hl+ml);

  szt = (sz < szi ? sz : szi);

  /* first packet */
  cb.add_data( data, szt, false );
  sz -= szt;

  /* next packets */
  for( size_t i=2;i<(apacket->niovecs && sz>0);++i )
  {
    szi  = apacket->wirevec[i].iov_len;
    data = (unsigned char *)apacket->wirevec[i].iov_base;
    cb.add_data( data, szi, false );
    sz -= szi;
    szv += szi;
  }

  /* fix last iov length : this is kind of iffy: if the data in the call
     is longer then one packet but the client inserts one shorter packet
     into the stream then some bytes will be inserted into the buffer.
     To prevent that, the iov size must be adjusted */
  if( szv > rx_GetDataSize(apacket) )
  {
    szt = szv-rx_GetDataSize(apacket);
    /* cut surplus */
    apacket->wirevec[apacket->niovecs-1].iov_len -= szt;
  }

  /* check mac */
  data = (unsigned char *)(apacket->wirevec[1].iov_base);
  data += hl;

  cb.finalize( mac );

  if( memcmp(mac,data,ml) == 0 )
  {
    /* OK, MAC matches */
    cpriv->n_check_packet_ok(true);
    return 0;
  }
  else
  {
    /* MAC mismatch */
    cpriv->n_check_packet_err(true);
    E(); return ERR_MAC_MISMATCH;
  }
}

/* only used to gather packet statistics */

/**
   @brief Send packet (called by Rx)

   This function is only used to track the valid and invalid
   outgoing packets.
*/
RT0D4_CDECL
int rt0d4_rx_SendPacket(
                struct rx_securityClass * aobj,
                struct rx_call * acall,
                struct rx_packet * apacket)
{
  if( !aobj || !acall || !apacket ) { E(); return ERR_INVALID_PARAM; }

  struct rx_connection * aconn = (struct rx_connection *)rx_ConnectionOf(acall);

  if( !aconn ) { E(); return ERR_INVALID_STATE; }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;

  if( !cpriv ) { E(); return ERR_INVALID_STATE; }

  if( cpriv->get_authenticated() )
  {
    cpriv->n_send_packet_ok(true);
  }
  else if( cpriv->n_prepare_packet_ok() == 0 )
  {
    cpriv->n_send_packet_err( true );
  }
  return 0;
}

/**
   @brief Get response (called by Rx)

   Client function that reads the challenge, generates the response and
   sends it.

   The response packet is expected to fit into one contigous packet.
*/
RT0D4_CDECL
int rt0d4_rx_GetResponse(
                struct rx_securityClass * aobj,
                struct rx_connection * aconn,
                struct rx_packet * apacket)
{
  if( !aobj || !aconn || !apacket ) { E(); return ERR_INVALID_PARAM; }

  conn_priv * cpriv = (conn_priv *)aconn->securityData;
  secobj_priv * sepr = (secobj_priv *)aobj->privateData;

  if( !cpriv || !sepr ) { E(); return ERR_INVALID_STATE; }

  /* defaults to unathenticated */
  cpriv->set_authenticated( false );

  /* push challenge in */
  if( cpriv->set_challenge(
                (unsigned char *)rx_DataOf(apacket),
                (size_t)rx_Contiguous(apacket)) == false )
  {
    cpriv->n_get_response_err( true );
    E(); return ERR_CHALLENGE_INVALID;
  }

  /* pull response out */
  size_t response_len = 0;
  const unsigned char * response = cpriv->get_response(response_len);

  if( !response_len || !response )
  {
    cpriv->n_get_response_err( true );
    E(); return ERR_CREATE_RESPONSE;
  }

  /* check certificate if required */
  if( cpriv->get_session_key() )
  {
    validate_peer_cert certcb = NULL;

    if( (certcb = sepr->get_peer_cert_validation_cb()) != NULL )
    {
      size_t sz;
      const unsigned char * crt = cpriv->get_peer_cert(sz);
      if( !sz || !crt || certcb(crt, sz) )
      {
        cpriv->n_check_response_err( true );
        E(); return ERR_SERVER_CERT_BAD;
      }
    }
  }

  rx_packetwrite( apacket,
    0,
    response_len,
    (char *)response );

  rx_SetDataSize(apacket, response_len);

  /* check if the created response can fit into one contigous packet */
  if( response_len <= (size_t)rx_Contiguous(apacket) )
  {
    if( cpriv->get_session_key() )
    {
      cpriv->set_authenticated( true );
      cpriv->n_get_response_ok(true);
      return 0; /* SUCCESS */
    }
    else
    {
      cpriv->n_get_response_err(true);
      E(); return ERR_CREATE_SESSIONKEY;
    }
  }
  else
  {
    cpriv->n_get_response_err(true);
    E(); return ERR_RESPONSE_TOO_BIG;
  }
}

/*********************************/
/* common for client and server */
/*********************************/

RT0D4_CDECL
int rt0d4_rx_AddAlgorithms(
                struct rx_securityClass * sec_obj,
                const char ** algs)
{
  if( !sec_obj || !algs || !(*algs) || strlen(*algs) == 0 )
  {
    E(); return ERR_INVALID_PARAM;
  }

  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }

  if( !secpr->add_algoritms( algs ) )
  {
    E(); return ERR_INTERNAL_ERROR;
  }
  return 0;
}

RT0D4_CDECL
int rt0d4_rx_SetEcKeyStrength(
                struct rx_securityClass * sec_obj,
                int min_strength,
                int max_strength)
{
  if( !sec_obj || (min_strength > max_strength) ||
      min_strength < 160 || max_strength > 600 )
  {
    E(); return ERR_INVALID_PARAM;
  }

  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }

  if( !secpr->set_eckey_strength( min_strength, max_strength ) )
  {
    E(); return ERR_INTERNAL_ERROR;
  }
  return 0;
}

RT0D4_CDECL
int rt0d4_rx_SetOwnCert(
                struct rx_securityClass * sec_obj,
                const unsigned char * cert,
                size_t len)
{
  if( !sec_obj || !cert || !len ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_own_cert( cert, len );
  return 0;
}

RT0D4_CDECL
int rt0d4_rx_SetPeerCertValidateCallBack(
                struct rx_securityClass * sec_obj,
                validate_peer_cert cb)
{
  if( !sec_obj || !cb ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_peer_cert_validation_cb( cb );
  return 0;
}


/** @brief the default algorithms used in rt0d4/Rx */
static const char * rt0d4_rx_algs_subset_default[] = {
  "prime192v3", // rel speed  7 : >80b
  "secp224r1",  // rel speed 10 : >80b
  "prime256v1", // rel speed 13 : 128b
  "secp384r1",  // rel speed 33 : 192b
  "secp521r1",  // rel speed 65 : 256b
  NULL
};

/*********************************/
/* client only functions         */
/*********************************/

static struct rx_securityClass::rx_securityOps rt0d4_rx_client_ops = {
    rt0d4_rx_Close,
    rt0d4_rx_NewConnection,
    rt0d4_rx_PreparePacket,
    rt0d4_rx_SendPacket,
    rt0d4_rx_CheckAuthentication,
    0,
    0,
    rt0d4_rx_GetResponse,
    0,
    rt0d4_rx_CheckPacket,
    rt0d4_rx_DestroyConnection,
    0,
    0,
    0,
    0
};

RT0D4_CDECL
struct rx_securityClass * rt0d4_rx_NewClientSecurityObject(void)
{
  struct rx_securityClass * ret = new struct rx_securityClass;
  struct rx_securityClass init = { &rt0d4_rx_client_ops, 0, 0 };
  *ret = init;

  bool is_server = false;
  secobj_priv * secpr = new secobj_priv(is_server);

  /* set defaults */
  secpr->add_algoritms( rt0d4_rx_algs_subset_default );
  secpr->set_eckey_strength( 192,521 );
  secpr->encrypt_packets( true );

  ret->privateData = secpr;
  return ret;
}

RT0D4_CDECL
void rt0d4_rx_DestroyClientSecurityObject(struct rx_securityClass * sec)
{
  if( sec ) delete sec;
}

RT0D4_CDECL
int rt0d4_rx_SetOwnCredentials(
                struct rx_securityClass * sec_obj,
                const char * login,
                const char * password)
{
  if( !sec_obj || !login || !password ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_own_credentials( login, password );
  return 0;
}

/*********************************/
/* server only functions         */
/*********************************/

static struct rx_securityClass::rx_securityOps rt0d4_rx_server_ops = {
    rt0d4_rx_Close,
    rt0d4_rx_NewConnection,
    rt0d4_rx_PreparePacket,
    rt0d4_rx_SendPacket,
    rt0d4_rx_CheckAuthentication,
    rt0d4_rx_CreateChallenge,
    rt0d4_rx_GetChallenge,
    0,
    rt0d4_rx_CheckResponse,
    rt0d4_rx_CheckPacket,
    rt0d4_rx_DestroyConnection,
    0,
    0,
    0,
    0
};

static struct rx_securityClass rt0d4_rx_server_object = {
  &rt0d4_rx_server_ops,
  0,
  0
};

RT0D4_CDECL
struct rx_securityClass * rt0d4_rx_NewServerSecurityObject(void)
{
  bool is_server = true;
  secobj_priv * secpr = new secobj_priv(is_server);

  /* set defaults */
  secpr->add_algoritms( rt0d4_rx_algs_subset_default );
  secpr->set_eckey_strength( 192,521 );
  secpr->encrypt_packets( true );

  rt0d4_rx_server_object.privateData = secpr;
  return &rt0d4_rx_server_object;
}

RT0D4_CDECL
int rt0d4_rx_SetPeerCredentialsValidateCallBack(
                struct rx_securityClass * sec_obj,
                validate_peer_credentials cb)
{
  if( !sec_obj || !cb ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_peer_credentials_validation_cb( cb );
  return 0;
}

RT0D4_CDECL
int rt0d4_rx_SetConnDataCreateCallBack(
                struct rx_securityClass * sec_obj,
                create_conn_data cb)
{
  if( !sec_obj || !cb ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_create_conn_data_cb(cb);
  return 0;
};


RT0D4_CDECL
int rt0d4_rx_SetConnDataDestroyCallBack(
                struct rx_securityClass * sec_obj,
                destroy_conn_data cb)
{
  if( !sec_obj || !cb ) { E(); return ERR_INVALID_PARAM; }
  secobj_priv * secpr = (secobj_priv *)sec_obj->privateData;
  if( !secpr ) { E(); return ERR_INVALID_STATE; }
  secpr->set_destroy_conn_data_cb(cb);
  return 0;
};

RT0D4_CDECL
int rt0d4_rx_SetConnData(
                struct rx_call * acall,
                void * data)
{
  if( !acall || !(acall->conn) ) { E(); return ERR_INVALID_PARAM; }
  conn_priv * cpriv = (conn_priv *)acall->conn->securityData;
  if( !cpriv ) { E(); return ERR_INVALID_STATE; }
  cpriv->set_conn_data( data );  
  return 0;
};

RT0D4_CDECL
void * rt0d4_rx_GetConnData( struct rx_call * acall )
{
  if( !acall || !(acall->conn) ) { E(); return 0; }
  conn_priv * cpriv = (conn_priv *)acall->conn->securityData;
  if( !cpriv ) { E(); return 0; }
  return cpriv->get_conn_data();
};

RT0D4_CDECL
const char * rt0d4_rx_GetConnLogin( struct rx_call * acall )
{
  if( !acall || !(acall->conn) ) { E(); return 0; }
  conn_priv * cpriv = (conn_priv *)acall->conn->securityData;
  if( !cpriv ) { E(); return 0; }
  return cpriv->get_peer_login();
};


RT0D4_CDECL
const char * rt0d4_rx_GetConnPassword( struct rx_call * acall )
{
  if( !acall || !(acall->conn) ) { E(); return 0; }
  conn_priv * cpriv = (conn_priv *)acall->conn->securityData;
  if( !cpriv ) { E(); return 0; }
  return cpriv->get_peer_password();
};


RT0D4_CDECL
const unsigned char * rt0d4_rx_GetConnCert(
                struct rx_call * acall,
                size_t * len)
{
  if( !acall || !(acall->conn) || !len ) { E(); return 0; }
  conn_priv * cpriv = (conn_priv *)acall->conn->securityData;
  if( !cpriv ) { E(); return 0; }
  return cpriv->get_peer_cert(*len);
};

/* EOF */
